#!/usr/bin/env python3
"""
Side-Channel Attack实验报告Generate器
Read实验ResultDirectory中的AllJSON报告And图像，Generate综合HTML报告页面
"""

import json
import argparse
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any
import base64
from scipy import stats
import numpy as np


class ExperimentReportGenerator:
    """实验报告Generate器"""
    
    def __init__(self, results_dir: str, output_file: str = None):
        self.results_dir = Path(results_dir)
        self.output_file = output_file or str(self.results_dir / "experiment_report.html")
        self.summary_data = None
        self.reports = []
        self.images = []
        
    def load_data(self):
        """LoadAllData"""
        print(f"正In扫描Directory: {self.results_dir}")
        
        summary_file = self.results_dir / "experiment_summary.json"
        if summary_file.exists():
            with open(summary_file, 'r', encoding='utf-8') as f:
                self.summary_data = json.load(f)
            print(f"  已Load实验总结")
        
        for json_file in self.results_dir.glob("*_report.json"):
            try:
                with open(json_file, 'r', encoding='utf-8') as f:
                    report = json.load(f)
                    report['_filename'] = json_file.name
                    self.reports.append(report)
                print(f"  已Load报告: {json_file.name}")
            except Exception as e:
                print(f"  Warning: No法Load {json_file.name}: {e}")
        
        plots_dir = self.results_dir / "plots"
        if plots_dir.exists():
            for img_file in plots_dir.glob("*.png"):
                self.images.append(img_file)
            print(f"  已找To {len(self.images)} 个图像File")
        
        print(f"\nDataLoadCompleted: 报告数量={len(self.reports)}, 图像数量={len(self.images)}")
        return len(self.reports) > 0
    
    def image_to_base64(self, image_path: Path) -> str:
        """将图像ConvertForbase64编码"""
        try:
            with open(image_path, 'rb') as f:
                img_data = f.read()
                return base64.b64encode(img_data).decode('utf-8')
        except Exception as e:
            print(f"Warning: No法Read图像 {image_path}: {e}")
            return ""
    
    def analyze_report(self, report: Dict[str, Any]) -> Dict[str, Any]:
        """AnalyzeSingle报告"""
        # Frommetadata中ExtractBasicInformation
        metadata = report.get('metadata', {})
        config = report.get('configuration', {})
        
        analysis = {
            'filename': report.get('_filename', 'unknown'),
            'algorithm': metadata.get('algorithm') or config.get('algorithm') or report.get('algorithm', 'unknown'),
            'attack_types': [],
            'success_bytes': 0,
            'total_bytes': 0,
            'avg_confidence': 0.0,
            'max_confidence': 0.0,
            'successful_attacks': [],
            'failed_attacks': [],
            'leakage_model': metadata.get('leakage_model') or config.get('leakage_model') or report.get('leakage_model', 'unknown'),
            'target_operation': metadata.get('target_operation') or config.get('target_operation') or report.get('target_operation', 'unknown'),
            'fuzzy_match_info': {},
            'byte_details': [],
            'top_n': metadata.get('top_n', 100)
        }
        
        # If还Yesunknown，TryFromFile名Parse
        if analysis['target_operation'] == 'unknown':
            filename = analysis['filename']
            # File名Format: aes_first_round_hd_20251014_151104_report.json
            parts = filename.replace('_report.json', '').split('_')
            if len(parts) >= 3:
                # Find可能的target_operation
                possible_ops = ['first_round', 'last_round', 'sbox_output', 'key_schedule']
                for i in range(len(parts) - 1):
                    combined = parts[i] + '_' + parts[i+1]
                    if combined in possible_ops:
                        analysis['target_operation'] = combined
                        break
        
        # ExtractattacksList中的Data（新Format）
        attacks = report.get('attacks', [])
        if attacks:
            for attack_data in attacks:
                attack_type = attack_data.get('attack_type', 'unknown')
                analysis['attack_types'].append(attack_type)
                
                # Extract正确Key
                correct_key_hex = attack_data.get('correct_key', '')
                if correct_key_hex:
                    analysis['correct_key'] = correct_key_hex
                    analysis['correct_key_bytes'] = [int(correct_key_hex[i:i+2], 16) for i in range(0, len(correct_key_hex), 2)]
                else:
                    analysis['correct_key'] = None
                    analysis['correct_key_bytes'] = []
                
                # Extract模糊MatchInformation
                fuzzy_match = attack_data.get('fuzzy_match', {})
                if fuzzy_match:
                    analysis['fuzzy_match_info'] = {
                        'in_top_n_count': fuzzy_match.get('in_top_n_count', 0),
                        'total_bytes': len(fuzzy_match.get('rankings', [])),
                        'avg_rank': fuzzy_match.get('avg_rank', 0),
                        'success_rate': fuzzy_match.get('success_rate', 0),
                        'rankings': fuzzy_match.get('rankings', [])
                    }
                
                # Useexact_match获取True实的Success字节数
                exact_match = attack_data.get('exact_match', {})
                correct_bytes = exact_match.get('correct_bytes', 0)
                total_bytes_reported = exact_match.get('total_bytes', 0)
                
                # Extract字节详情
                byte_details = attack_data.get('byte_details', [])
                analysis['total_bytes'] = len(byte_details) if byte_details else total_bytes_reported
                analysis['success_bytes'] = correct_bytes  # Useexact_match的Data
                
                # 收集All字节的置信度（用于平均Calculate）
                all_confidences = []
                
                for byte_detail in byte_details:
                    byte_idx = byte_detail.get('byte_index', 0)
                    confidence = byte_detail.get('confidence', 0.0)
                    top_candidates = byte_detail.get('top_candidates', [])
                    best_hyp = byte_detail.get('best_hypothesis')
                    
                    # 判断YesNoTrue正Success：best_hypothesisYesNoetc.于correct_key_bytes
                    actual_success = False
                    if analysis['correct_key_bytes'] and byte_idx < len(analysis['correct_key_bytes']):
                        actual_success = (best_hyp == analysis['correct_key_bytes'][byte_idx])
                    
                    analysis['byte_details'].append({
                        'index': byte_idx,
                        'success': actual_success,  # Use实际的Success判断
                        'confidence': confidence,
                        'best_hypothesis': best_hyp,
                        'top_candidates': top_candidates[:10]  # 只取前10个
                    })
                    
                    all_confidences.append(confidence)
                
                # Calculate平均置信度（UseAll字节）
                if all_confidences:
                    analysis['avg_confidence'] = sum(all_confidences) / len(all_confidences)
                    analysis['max_confidence'] = max(all_confidences)
        else:
            # 兼容旧Format
            results = report.get('results', {})
            for attack_type, attack_data in results.items():
                if not isinstance(attack_data, dict):
                    continue
                    
                analysis['attack_types'].append(attack_type)
                byte_results = attack_data.get('byte_results', {})
                
                if byte_results:
                    analysis['total_bytes'] = len(byte_results)
                    confidences = []
                    
                    for byte_idx, byte_data in byte_results.items():
                        success = byte_data.get('success', False)
                        confidence = byte_data.get('confidence', 0.0)
                        
                        if success:
                            analysis['success_bytes'] += 1
                            confidences.append(confidence)
                    
                    if confidences:
                        analysis['avg_confidence'] = sum(confidences) / len(confidences)
                        analysis['max_confidence'] = max(confidences)
        
        if analysis['total_bytes'] > 0:
            analysis['success_rate'] = analysis['success_bytes'] / analysis['total_bytes']
        else:
            analysis['success_rate'] = 0.0
        
        return analysis
    
    def calculate_statistical_significance(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
        """
        CalculateAttack的统计显著性
        
        Args:
            analysis: Single报告的AnalyzeResult
            
        Returns:
            统计显著性Result
        """
        stats_result = {
            'is_significant': False,
            'confidence_level': 0.0,
            'p_value': 1.0,
            'interpretation': 'Unknown',
            'details': {}
        }
        
        fuzzy_info = analysis.get('fuzzy_match_info', {})
        in_top_n = fuzzy_info.get('in_top_n_count', 0)
        total_bytes = fuzzy_info.get('total_bytes', 0)
        avg_rank = fuzzy_info.get('avg_rank', 0)
        top_n = analysis.get('top_n', 100)
        
        if total_bytes == 0:
            return stats_result
        
        # Method1: 二项检验 - 检验InTop-N中找To正确值的概率
        # H0: Attacketc.同于随机猜测 (p = top_n/256)
        # H1: Attack优于随机猜测 (p > top_n/256)
        random_prob = top_n / 256.0  # 随机猜测InTop-N中的概率
        
        # Use二项检验（单侧）
        binomial_test = stats.binomtest(in_top_n, total_bytes, random_prob, alternative='greater')
        p_value_binomial = binomial_test.pvalue
        
        # Method2: 排名检验 - 检验平均排名YesNo显著低于随机期望
        # If随机猜测，平均排名应该Yes (top_n + 1) / 2
        expected_rank_random = (top_n + 1) / 2.0
        
        # 收集All非Empty排名
        rankings = fuzzy_info.get('rankings', [])
        valid_ranks = [r for r in rankings if r is not None]
        
        rank_significant = False
        p_value_rank = 1.0
        if valid_ranks and avg_rank > 0:
            # Use单样本t检验
            # H0: 平均排名 = expected_rank_random
            # H1: 平均排名 < expected_rank_random (Attack更好)
            if len(valid_ranks) >= 2:
                t_stat, p_value_rank_two_sided = stats.ttest_1samp(valid_ranks, expected_rank_random)
                p_value_rank = p_value_rank_two_sided / 2 if t_stat < 0 else 1.0  # 单侧检验
                rank_significant = (p_value_rank < 0.05 and avg_rank < expected_rank_random)
        
        # 综合判断
        p_value = min(p_value_binomial, p_value_rank)
        is_significant = (p_value_binomial < 0.05) or rank_significant
        
        # Calculate置信水平
        confidence_level = (1 - p_value) * 100
        
        # 解释Result
        if p_value < 0.001:
            interpretation = "Highly Significant - Attack is highly effective"
            conclusion = "The attack significantly outperforms random guessing with very strong statistical evidence."
        elif p_value < 0.01:
            interpretation = "Very Significant - Attack is very effective"
            conclusion = "The attack significantly outperforms random guessing with strong statistical evidence."
        elif p_value < 0.05:
            interpretation = "Significant - Attack is effective"
            conclusion = "The attack significantly outperforms random guessing with moderate statistical evidence."
        elif p_value < 0.10:
            interpretation = "Marginally Significant - Attack shows promise"
            conclusion = "The attack shows some evidence of effectiveness, but results are not conclusive."
        else:
            interpretation = "Not Significant - Attack resembles random guessing"
            conclusion = "The attack does not significantly outperform random guessing. Results may be due to chance."
        
        # Calculate效果大小 (Effect Size)
        observed_rate = in_top_n / total_bytes if total_bytes > 0 else 0
        effect_size = (observed_rate - random_prob) / random_prob if random_prob > 0 else 0
        
        stats_result = {
            'is_significant': is_significant,
            'confidence_level': confidence_level,
            'p_value': p_value,
            'p_value_binomial': p_value_binomial,
            'p_value_rank': p_value_rank,
            'interpretation': interpretation,
            'conclusion': conclusion,
            'details': {
                'observed_success_rate': observed_rate,
                'random_success_rate': random_prob,
                'improvement_factor': observed_rate / random_prob if random_prob > 0 else 0,
                'effect_size': effect_size,
                'observed_avg_rank': avg_rank,
                'expected_random_rank': expected_rank_random,
                'rank_improvement': expected_rank_random - avg_rank if avg_rank > 0 else 0,
                'valid_ranks_count': len(valid_ranks)
            }
        }
        
        return stats_result
    
    def compare_reports(self) -> Dict[str, Any]:
        """对比All报告"""
        if not self.reports:
            return {}
        
        analyses = [self.analyze_report(r) for r in self.reports]
        comparison = {
            'total_reports': len(self.reports),
            'by_leakage_model': {},
            'by_target_operation': {},
            'best_result': None,
            'worst_result': None
        }
        
        for analysis in analyses:
            model = analysis['leakage_model']
            if model not in comparison['by_leakage_model']:
                comparison['by_leakage_model'][model] = []
            comparison['by_leakage_model'][model].append(analysis)
            
            operation = analysis['target_operation']
            if operation not in comparison['by_target_operation']:
                comparison['by_target_operation'][operation] = []
            comparison['by_target_operation'][operation].append(analysis)
        
        if analyses:
            analyses.sort(key=lambda x: (x['success_rate'], x['avg_confidence']), reverse=True)
            comparison['best_result'] = analyses[0]
            comparison['worst_result'] = analyses[-1]
        
        return comparison
    
    def generate_html(self):
        """GenerateHTML报告"""
        if not self.reports:
            print("Error: 没Has找To任何报告File")
            return False
        
        print("\n正InGenerateHTML报告...")
        analyses = [self.analyze_report(r) for r in self.reports]
        comparison = self.compare_reports()
        
        from report_html_generator import HTMLGenerator
        generator = HTMLGenerator(self.results_dir)
        html = generator.generate(analyses, comparison, self.summary_data, self.images, self)
        
        with open(self.output_file, 'w', encoding='utf-8') as f:
            f.write(html)
        
        print(f"\n报告已Generate: {self.output_file}")
        return True


def main():
    parser = argparse.ArgumentParser(description="Side-Channel Attack实验报告Generate器")
    parser.add_argument('results_dir', help='实验ResultDirectoryPath')
    parser.add_argument('-o', '--output', help='OutputHTMLFilePath')
    parser.add_argument('--no-images', action='store_true', help='Not嵌入图像')
    
    args = parser.parse_args()
    
    generator = ExperimentReportGenerator(args.results_dir, args.output)
    
    if not generator.load_data():
        print("Error: 未找To任何报告Data")
        return 1
    
    if generator.generate_html():
        print("\nSuccess!")
        return 0
    else:
        print("\nFailed!")
        return 1


if __name__ == "__main__":
    import sys
    sys.exit(main())
